﻿using System;
using System.Collections.Generic;
using Axiom.Math;
using Axiom.Core;

namespace Engine.Navigation 
{
    public class GlobeProjection
    {
        private Globe globe;
        public Globe Globe
        {
            get { return globe; }
        }

        private float halfHeight = 90.0f;
        private float halfWidth = 180.0f;

        public GlobeProjection(Globe globe)
        {
            this.globe = globe;
        }

        public Vector3 ToGlobe(Vector3 point)
        {
            Vector3 v = Vector3.Zero;
 
			double x1, y1, z1;
			float radius = globe.Radius + point.z;

			double angleAzimuthDegree = -1 * point.x;
			double angleAzimuthRadian = (System.Math.PI / 180.0d) * angleAzimuthDegree;

			double angleElevationDegree = halfHeight - point.y;
			double angleElevationRadian = (System.Math.PI / 180.0d) * angleElevationDegree;

			double ratioRadius = radius * System.Math.Cos(angleElevationRadian);
            double innerRadius = System.Math.Sqrt(System.Math.Pow(radius, 2f) - System.Math.Pow(ratioRadius, 2f));

			if (Double.IsNaN(innerRadius))
			{
				x1 = z1 = 0.0d;
			}
			else
			{
				x1 = innerRadius * System.Math.Cos(angleAzimuthRadian);
				z1 = innerRadius * System.Math.Sin(angleAzimuthRadian);
			}
			y1 = ratioRadius;
			return new Vector3((float)x1, (float)y1, (float)z1);
        }

        public Vector3 ToGlobe(Vector2 point)
        {
            Vector3 _point = new Vector3(point.x, point.y, 0.0f);   
            return ToGlobe(_point);
        }

        public Vector3 ToPlanar(Vector3 point)
        {
            Vector3 v = Vector3.Zero;

			double x1, y1, z1;
			double west_east = point.z / System.Math.Abs(point.z);
			if (Double.IsNaN(west_east))
			{
				west_east = 1.0d;
			}
            float radius = globe.Radius;
			z1 = point.Length - radius;

			point /= point.Length;
			point *= radius;

			double ratioRadius = point.y;
			double innerRadius = System.Math.Sqrt(System.Math.Pow(radius, 2f) - System.Math.Pow(ratioRadius, 2f));


			double angleElevationRadian = System.Math.Acos(ratioRadius / radius);
			double angleAzimuthRadian = System.Math.Acos(point.x / innerRadius);
			//double angleAzimuthRadian2 = System.Math.Asin(point.z / innerRadius);

			double angleElevationDegree = angleElevationRadian / (System.Math.PI / 180.0d);
			double angleAzimuthDegree = angleAzimuthRadian / (System.Math.PI / 180.0d);
			//double angleAzimuthDegree2 = angleAzimuthRadian2 / (System.Math.PI / 180.0d);


			y1 = halfHeight - angleElevationDegree;
			x1 = angleAzimuthDegree * -1 * west_east;

			return new Vector3((float)x1, (float)y1, (float)z1);
        }

        /// <summary> make 2D normalized coords</summary>
        public Vector2 ToScreen(Vector3 point3d, Camera camera)
        {
            //Vector4 p = new Vector4(point3d.x, point3d.y, point3d.z, 1f);
            Vector3 point = camera.ProjectionMatrix * camera.ViewMatrix * point3d;

            // (dividing out by the Z depth - this is an approximation) TODO: how to be accurate?
            float x = point.x / point.z;
            float y = -(point.y / point.z);
            // now both x and y vary in range <-1, 1> where [-1,-1] is the left top corner and [1,1] is the right bottom corner

            //yet normalize to range <0,1> and return the result
            return new Vector2(x * 0.5f + 0.5f, y * 0.5f + 0.5f);
        }
    }
}
